<?php

/**
 * access.php
 * 
 * workPHP控制器基类 
 * @author wang <wang@fvzone.com>
 * @copyright  Copyright (c) 2010 Fire vision Software Studio
 * @link http://www.fvzone.com
 * @license New BSD License.{@link http://www.opensource.org/licenses/bsd-license.php}
 * @version $Id: Controller.php 1.1 2011-8-12 22:48:01Z wang $
 * @package work.libraries
 * @subpackage access
 * @since 1.0
 */

import('work.libraries.access.rules');

class access
{
	/**
	 * Array of view levels
	 *
	 * @var    array
	 * @since  11.1
	 */
	protected static $viewLevels = array();

	/**
	 * Array of rules for the asset
	 *
	 * @var    array
	 * @since  11.1
	 */
	protected static $assetRules = array();

	/**
	 * Method to check if a user is authorised to perform an action, optionally on an asset.
	 *
	 * @param   integer  $userId  Id of the user for which to check authorisation.
	 * @param   string   $action  The name of the action to authorise.
	 * @param   mixed    $asset   Integer asset id or the name of the asset as a string.  Defaults to the global asset node.
	 *
	 * @return  boolean  True if authorised.
	 *
	 * @since   11.1
	 */
	public static function check($userId, $action, $asset = null)
	{
		//Sanitise inputs.
		$userId = (int) $userId;

		$action = strtolower(preg_replace('#[\s\-]+#', '.', trim($action)));
		$asset  = strtolower(preg_replace('#[\s\-]+#', '.', trim($asset)));

		//Default to the root asset node.
		if (empty($asset)) {
			$asset = 1;
		}

		//Get the rules for the asset recursively to root if not already retrieved.
		if (empty(self::$assetRules[$asset])) {
			self::$assetRules[$asset] = self::getAssetRules($asset, true);
		}

		//Get all groups against which the user is mapped.
		$identities = self::getGroupsByUser($userId);
		array_unshift($identities, $userId * -1);

		return self::$assetRules[$asset]->allow($action, $identities);
	}

	/**
	 * Method to check if a group is authorised to perform an action, optionally on an asset.
	 *
	 * @param   integer  $groupId  The path to the group for which to check authorisation.
	 * @param   string   $action   The name of the action to authorise.
	 * @param   mixed    $asset    Integer asset id or the name of the asset as a string.  Defaults to the global asset node.
	 *
	 * @return  boolean  True if authorised.
	 *
	 * @since   11.1
	 */
	public static function checkGroup($groupId, $action, $asset = null)
	{
		//Sanitize inputs.
		$groupId = (int) $groupId;
		$action = strtolower(preg_replace('#[\s\-]+#', '.', trim($action)));
		$asset  = strtolower(preg_replace('#[\s\-]+#', '.', trim($asset)));

		//Get group path for group
		$groupPath = self::getGroupPath($groupId);

		//Default to the root asset node.
		if (empty($asset)) {
			$asset = 1;
		}

		//Get the rules for the asset recursively to root if not already retrieved.
		if (empty(self::$assetRules[$asset])) {
			self::$assetRules[$asset] = self::getAssetRules($asset, true);
		}

		return self::$assetRules[$asset]->allow($action, $groupPath);
	}

	/**
	 * Gets the parent groups that a leaf group belongs to in its branch back to the root of the tree
	 * (including the leaf group id).
	 *
	 * @param   mixed  $groupId  An integer or array of integers representing the identities to check.
	 *
	 * @return  mixed  True if allowed, false for an explicit deny, null for an implicit deny.
	 *
	 * @since   11.1
	 */
	protected static function getGroupPath($groupId)
	{
		static $groups, $paths;

		//Preload all groups
		if (empty($groups)) {
            $res = Work::instance('Model')->execute('SELECT parent.id, parent.lft, parent.rgt FROM `#__acl_groups` AS parent ORDER BY parent.lft');
            if (is_array($res)) {
                $groups = array();
                foreach ($res as $v) {
                    $groups[$v['id']] = (object) $v;
                }
            }
		}

		//Make sure groupId is valid
		if (!array_key_exists($groupId, $groups)) 
        {
			return array();
		}

		//Get parent groups and leaf group
		if (!isset($paths[$groupId])) 
        {
			$paths[$groupId] = array();

			foreach ($groups as $group)
			{
				if ($group->lft <= $groups[$groupId]->lft && $group->rgt >= $groups[$groupId]->rgt) {
					$paths[$groupId][] = $group->id;
				}
			}
		}

		return $paths[$groupId];
	}

	/**
	 * Method to return the JRules object for an asset.  The returned object can optionally hold
	 * only the rules explicitly set for the asset or the summation of all inherited rules from
	 * parent assets and explicit rules.
	 *
	 * @param   mixed    $asset      Integer asset id or the name of the asset as a string.
	 * @param   boolean  $recursive  True to return the rules object with inherited rules.
	 *
	 * @return  JRules   JRules object for the asset.
	 *
	 * @since   11.1
	 */
	public static function getAssetRules($asset, $recursive = false)
	{
		//Get the database connection object.

		//Build the database query to get the rules for the asset.
		$sql = 'SELECT ';
        $sql .= $recursive ? 'b.rules' : 'a.rules';
        $sql .= ' FROM `#__acl_assets` AS a ';

		//If we want the rules cascading up to the global asset node we need a self-join.
		if ($recursive) 
        {
            $sql .= ' LEFT JOIN `#__acl_assets` AS b ON b.lft <= a.lft AND b.rgt >= a.rgt ';
		}

		//If the asset identifier is numeric assume it is a primary key, else lookup by name.
		if (is_numeric($asset)) 
        {
            $sql .= " WHERE a.id = $asset ";
		}
		else 
        {
            $sql .= " WHERE a.name = '$asset' ";
		}

		//If we want the rules cascading up to the global asset node we need a self-join.
		if ($recursive) {
            $sql .= ' ORDER BY b.lft ';
		}

		//Execute the query and load the rules from the result.
        $result = Work::instance('Model')->execute($sql);
        $result = self::getDbColumn($result, 'rules');

		//Get the root even if the asset is not found and in recursive mode
		if ($recursive && empty($result))
        {
            //取得一条数据
            $result = Work::instance('Model')->execute('SELECT rules FROM `#__acl_assets` WHERE parent_id = 0');
            $result = self::getDbColumn($result, 'rules');
		}

		//Instantiate and return the JRules object for the asset rules.
		$rules	= new rules;
		$rules->mergeCollection($result);
		return $rules;
	}

	/**
	 * Method to return a list of user groups mapped to a user. The returned list can optionally hold
	 * only the groups explicitly mapped to the user or all groups both explicitly mapped and inherited
	 * by the user.
	 *
	 * @param   integer  $userId     Id of the user for which to get the list of groups.
	 * @param   boolean  $recursive  True to include inherited user groups.
	 *
	 * @return  array    List of user group ids to which the user is mapped.
	 *
	 * @since   11.1
	 */
	public static function getGroupsByUser($userId, $recursive = true)
	{
		static $results = array();

		//Creates a simple unique string for each parameter combination:
		$storeId = $userId.':'.(int) $recursive;

		if (!isset($results[$storeId])) 
        {
			//Guest user
			if (empty($userId)) 
            {
				$result = array(JComponentHelper::getParams('com_users')->get('guest_usergroup', 1));
			}
			//Registered user
			else 
            {
                $sql = 'SELECT ';
                $sql .= $recursive ? 'b.id' : 'a.id';
                $sql .= ' FROM `#__acl_user_group_map` AS map ';
				$sql .= ' LEFT JOIN `#__acl_groups` AS a ON (a.id = map.group_id) ';

				//If we want the rules cascading up to the global asset node we need a self-join.
				if ($recursive) {
                    $sql .= ' LEFT JOIN `#__acl_groups` AS b ON (b.lft <= a.lft AND b.rgt >= a.rgt) ';
				}
				$sql .= ' WHERE map.user_id = '.(int) $userId;

                $result = Work::instance('Model')->execute($sql);
                $result = self::getDbColumn($result, 'id');

				//Execute the query and load the rules from the result.
				if (empty($result)) 
                {
					$result = array('1');
				}
				else
                {
					$result = array_unique($result);
				}
			}
			$results[$storeId] = $result;
		}
		return $results[$storeId];
	}

	/**
	 * Method to return a list of user Ids contained in a Group
	 *
	 * @param   integer  $groupId    The group Id
	 * @param   boolean  $recursive  Recursively include all child groups (optional)
	 *
	 * @return  array
	 *
	 * @since   11.1
	 * @todo    This method should move somewhere else
	 */
	public static function getUsersByGroup($groupId, $recursive = false)
	{
		$test = $recursive ? '>=' : '=';

		//First find the users contained in the group
        $sql = 'SELECT DISTINCT(user_id) ';
        $sql .= ' FROM #__acl_groups as ug1 ';
        $sql .= ' INNER JOIN #__acl_groups AS ug2 ON ug2.lft'.$test.'ug1.lft AND ug1.rgt'.$test.'ug2.rgt ';
        $sql .= ' INNER JOIN #__acl_user_group_map AS m ON ug2.id=m.group_id ';
        $sql .= ' WHERE ug1.id = '.(int) $groupId;

        $result = Work::instance('Model')->execute($sql);
        $result = self::getDbColumn($result, 'user_id');

		return $result;
	}

	/**
	 * Method to return a list of view levels for which the user is authorised.
	 *
	 * @param   integer  $userId  Id of the user for which to get the list of authorised view levels.
	 *
	 * @return  array    List of view levels for which the user is authorised.
	 *
	 * @since   11.1
	 */
	public static function getAuthorisedViewLevels($userId)
	{
		//Get all groups that the user is mapped to recursively.
		$groups = self::getGroupsByUser($userId);

		//Only load the view levels once.
		if (empty(self::$viewLevels)) 
        {
			//Build the base query.
            $result = Work::instance('Model')->execute('SELECT id, rules FROM #__acl_viewlevels');

			//Build the view levels array.
			foreach ($result as $level)
			{
				self::$viewLevels[$level['id']] = (array) json_decode($level['rules']);
			}
		}

		//Initialise the authorised array.
		$authorised = array(1);

		//Find the authorised levels.
		foreach (self::$viewLevels as $level => $rule)
		{
			foreach ($rule as $id)
			{
				if (($id < 0) && (($id * -1) == $userId)) {
					$authorised[] = $level;
					break;
				}
				//Check to see if the group is mapped to the level.
				elseif (($id >= 0) && in_array($id, $groups)) {
					$authorised[] = $level;
					break;
				}
			}
		}
		return $authorised;
	}
    
    /**
     * 获取数据库查询结果的列
     */
    public static function getDbColumn($data, $key = null)
    {
        if (is_array($data)) 
        {
            $r = array();
            foreach ($data as $v) 
            {
                $r[] = $v[$key];
            }
        }
        return $r;
    }
}
